package de.blitzcoder.collide.docs;

import de.blitzcoder.collide.Config;
import de.blitzcoder.collide.engine.parser.CodeConstant;
import de.blitzcoder.collide.engine.parser.CodeFunction;
import de.blitzcoder.collide.engine.parser.CodeGlobal;
import de.blitzcoder.collide.engine.parser.CodeInclude;
import de.blitzcoder.collide.engine.parser.CodeMethod;
import de.blitzcoder.collide.engine.parser.CodeModuleInfo;
import de.blitzcoder.collide.engine.parser.CodeObject;
import de.blitzcoder.collide.engine.parser.CodeParser;
import de.blitzcoder.collide.engine.parser.CodeType;
import de.blitzcoder.collide.gui.SplashScreen;
import de.blitzcoder.collide.util.Log;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.ListIterator;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;

/**
 *
 * @author blitzcoder
 */
public class DocParser {

    // Serialized documentation file name
    private static final String DOCFILE = "maxdocs.obj";

    // List of parsed files.
    // Some bmx-files are included/imported by many other files
    // These list makes shure, that every file is only parsed once
    private static LinkedList<File> parsedFiles = new LinkedList<File>();

    public static void deleteSavedDocs() {
        File file = new File(DOCFILE);
        if (file.exists()) {
            file.delete();
        }
    }

    public static void parse(SplashScreen splash) {

        // For first, check if some saved docs area available
        File docs = new File(DOCFILE);
        if (docs.exists()) {
            try {
                ObjectInputStream stream = new ObjectInputStream(new FileInputStream(docs));

                // Read the docs
                Object obj = stream.readObject();
                if (obj instanceof LinkedList) {
                    Documentation.scopeList = (LinkedList) obj;
                }

                // Read the keyword list
                obj = stream.readObject();
                if (obj instanceof LinkedList) {
                    Documentation.keywordList = (LinkedList) obj;
                }

                stream.close();

                return;
            } catch (Exception ex) {
                Log.log("Loading serialized docs failed. Trying to parse...");
                docs.delete();
            }
        }

        File dir = null;

        if (Config.getFileProperty("system.bmk").exists()) {
            dir = Config.getFileProperty("system.bmk");
            dir = new File(dir.getParentFile().getParentFile(), "mod");
        }

        if (dir == null || !dir.exists()) {
            JFileChooser chooser = new JFileChooser();
            
            chooser.setDialogTitle("Bitte wählen sie den BlitzMax-Ordner aus!");
            chooser.setFileSelectionMode(chooser.DIRECTORIES_ONLY);
            int ret = chooser.showOpenDialog(splash);
            if (ret == chooser.APPROVE_OPTION) {
                dir = chooser.getSelectedFile();
                dir = new File(dir, "mod");
                if (!dir.exists()) {
                    JOptionPane.showMessageDialog(splash, "Falscher Ordner! Bitte wählen sie den BlitzMax-Order aus!");
                    parse(splash);
                } else {
                    if (System.getProperty("os.name").toLowerCase().indexOf("windows") != -1) {
                        Config.setFileProperty("system.bmk", new File(new File(dir.getParentFile(), "bin"), "bmk.exe"));
                    } else {
                        Config.setFileProperty("system.bmk", new File(new File(dir.getParentFile(), "bin"), "bmk"));
                    }
                }
            }
        }

        if (dir == null || !dir.exists()) {
            Documentation.scopeList = new LinkedList<Scope>();
            return;
        }

        parse(dir, splash);

    }

    public static void parse(File modFolder, SplashScreen splash) {

        // List of scopes
        LinkedList<Scope> scopeList = new LinkedList<Scope>();

        // Find Scopes
        String[] scopes = modFolder.list();
        for (int i = 0; i < scopes.length; i++) {
            if (scopes[i].endsWith(".mod")) {
                scopeList.addLast(new Scope(scopes[i].replace(".mod", "")));
            }
        }

        // Find mods
        ListIterator<Scope> it = scopeList.listIterator();
        while (it.hasNext()) {
            Scope scope = it.next();
            File scopePath = new File(modFolder, scope.getName() + ".mod");
            String[] mods = scopePath.list();
            for (int i = 0; i < mods.length; i++) {
                if (mods[i].endsWith(".mod")) {
                    scope.addModule(new Module(mods[i].replace(".mod", ""),scope));
                }
            }

        }

        it = scopeList.listIterator();
        while (it.hasNext()) {
            Scope scope = it.next();
            ListIterator<Module> modIt = scope.iterateModules();
            File scopeFolder = new File(modFolder, scope.getName() + ".mod");
            while (modIt.hasNext()) {
                Module mod = modIt.next();
                File folder = new File(scopeFolder, mod.getName() + ".mod");
                splash.setStatus("Lese Dokumentation: " + scope.getName() + "." + mod.getName());
                parseFile(mod, new File(folder, mod.getName() + ".bmx"));
            }
        }

        // Sort Scopes
        Collections.sort(scopeList, new Comparator<Scope>() {
            @Override
            public int compare(Scope s1, Scope s2) {
                return s1.getName().compareToIgnoreCase(s2.getName());
            }
        });

        // Set the right module for each codeobject and sort by name
        it = scopeList.listIterator();
        while (it.hasNext()) {
            Scope scope = it.next();

            // Sort Modules
            Collections.sort(scope.getModules(), new Comparator<Module>() {
                @Override
                public int compare(Module m1, Module m2) {
                    return m1.getName().compareToIgnoreCase(m2.getName());
                }
            });

            ListIterator<Module> modIt = scope.iterateModules();
            while (modIt.hasNext()) {
                Module mod = modIt.next();
                sortCodeObjects(mod.getContent());
                ListIterator<CodeObject> contentIt = mod.iterateContent();
                while (contentIt.hasNext()) {
                    CodeObject obj = contentIt.next();
                    obj.setModule(mod);
                    if (obj instanceof CodeType) {
                        ListIterator<CodeObject> typeIt = ((CodeType)obj).getChildren().listIterator();
                        while (typeIt.hasNext()) {
                            typeIt.next().setModule(mod);
                        }
                    }
                }
            }
        }


        splash.setStatus("Dokumentation wird gespeichert");

        Documentation.scopeList = scopeList;

        // serialize the docs
        try {
            File docs = new File(DOCFILE);

            if (docs.exists()) {
                docs.delete();
            }

            ObjectOutputStream stream = new ObjectOutputStream(new FileOutputStream(docs));

            stream.writeObject(Documentation.scopeList);
            stream.writeObject(Documentation.keywordList);

            stream.close();

        } catch (Exception ex) {
            Log.log("saving docs failed...");
            ex.printStackTrace();
        }

        

        // Write the blitzmax mode file
        // Collect all functions and methods
        StringBuffer buf = new StringBuffer();
        ListIterator<Scope> scopeIt = Documentation.scopeList.listIterator();
        while (scopeIt.hasNext()) {

            Scope scope = scopeIt.next();
            buf.append("<LITERAL2>");
            buf.append(scope.getName());
            buf.append("</LITERAL2>\n");

            ListIterator<Module> modIt = scope.iterateModules();
            while (modIt.hasNext()) {

                Module module = modIt.next();
                buf.append("<LITERAL2>");
                buf.append(module.getName());
                buf.append("</LITERAL2>\n");

                ListIterator<CodeObject> contentIt = module.iterateContent();
                while (contentIt.hasNext()) {

                    CodeObject obj = contentIt.next();

                    if (obj instanceof CodeConstant) {
                        buf.append("<LITERAL3>");
                        buf.append(obj.getName());
                        buf.append("</LITERAL3>\n");
                    } else if (obj instanceof CodeFunction || obj instanceof CodeMethod) {
                        buf.append("<FUNCTION>");
                        buf.append(obj.getName());
                        buf.append("</FUNCTION>\n");
                    }
                    if (obj instanceof CodeGlobal) {
                        buf.append("<LITERAL4>");
                        buf.append(obj.getName());
                        buf.append("</LITERAL4>\n");
                    } else if (obj instanceof CodeType) {
                        buf.append("<LITERAL2>");
                        buf.append(obj.getName());
                        buf.append("</LITERAL2>\n");
                    }

                }

            }


        }

        // Add the keywords
        ListIterator<String> keywordIt = Documentation.keywordList.listIterator();
        while (keywordIt.hasNext()) {
            buf.append("<KEYWORD2>");
            buf.append(keywordIt.next());
            buf.append("</KEYWORD2>\n");
        }


        String txt = buf.toString();

        // Write the file
        try {
            File file = new File("blitzmax.xml");
            BufferedReader reader = new BufferedReader(new FileReader(file));
            StringBuffer input = new StringBuffer();
            while (reader.ready()) {
                input.append(reader.readLine() + "\n");
            }
            reader.close();
            String inputTxt = input.toString();
            inputTxt = inputTxt.replace("<!--insertKeywords-->",txt);
            file.delete();
            FileWriter writer = new FileWriter(file);
            writer.write(inputTxt);
            writer.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }

    // --------------------------------
    // --------------------------------
    // --------------------------------
        /*
    JFrame window = new JFrame("Tree");
    window.setDefaultCloseOperation(window.EXIT_ON_CLOSE);
    window.setPreferredSize(new Dimension(400,800));

    JTree tree = new JTree();
    DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root");
    DefaultTreeModel model = new DefaultTreeModel(root);

    it = scopeList.listIterator();
    while (it.hasNext()) {
    Scope scope = it.next();
    DefaultMutableTreeNode scopeNode = new DefaultMutableTreeNode(scope);

    ListIterator<Module> modIt = scope.iterateModules();
    while (modIt.hasNext()) {

    Module mod = modIt.next();
    DefaultMutableTreeNode modNode = new DefaultMutableTreeNode(mod);

    ListIterator<CodeObject> contentIt = mod.iterateContent();
    while(contentIt.hasNext()) {

    CodeObject content = contentIt.next();
    DefaultMutableTreeNode contentNode = new DefaultMutableTreeNode(content.getClass().getCanonicalName()+": "+content.getName());

    model.insertNodeInto(contentNode, modNode, 0);

    }

    model.insertNodeInto(modNode, scopeNode, 0);

    }

    model.insertNodeInto(scopeNode, root, 0);
    }

    tree.setModel(model);

    window.getContentPane().add(new JScrollPane(tree));
    window.pack();
    window.setVisible(true);
     */
    }

    private static void sortCodeObjects(LinkedList<CodeObject> list) {
        sortCodeObjectList(list);

        // Sort content in types
        ListIterator<CodeObject> it = list.listIterator();
        while (it.hasNext()) {
            CodeObject obj = it.next();
            if (obj instanceof CodeType) {
                CodeType type = (CodeType)obj;
                sortCodeObjectList(type.getChildren());
            }
        }

    }
    
    private static void sortCodeObjectList(LinkedList<CodeObject> list) {
        Collections.sort(list,new Comparator<CodeObject>() {
            @Override
            public int compare(CodeObject o1, CodeObject o2) {
                return o1.getName().compareToIgnoreCase(o2.getName());
            }
        });
    }

    private static void parseFile(Module target, File file) {

        if (parsedFiles.contains(file)) {
            Log.log("Skipped parsing "+file.getName()+" (Already parsed)");
            return;
        }

        parsedFiles.addFirst(file);

        Log.log("Parsing " + file.getName());

        CodeParser parser = new CodeParser(true); // true = ignorePrivate

        String code = "";
        try {
            code = getFile(file);
        } catch (Exception ex) {
            Log.log("Failed to open file: " + file.getAbsolutePath() + " (" + ex.getClass().getName() + ")");
            return;
        }

        parser.parse(code,file);

        ListIterator<String> keywordIt = parser.getKeywords().listIterator();
        while (keywordIt.hasNext()) {
            Documentation.keywordList.addLast(keywordIt.next());
        }

        LinkedList<CodeObject> list = parser.getResult();

        ListIterator<CodeObject> it = list.listIterator();
        while (it.hasNext()) {
            CodeObject obj = it.next();
            if (obj instanceof CodeInclude) {
                CodeInclude inc = (CodeInclude) obj;
                parseFile(target, new File(file.getParentFile(), inc.getPath()));
            } else if (obj instanceof CodeModuleInfo) {
                parseModuleInfoLine(obj.getName(), target);
            } else {
                target.addContent(obj);
            }
        }

    }

    private static String getFile(File file) throws IOException {
        FileReader fileReader = new FileReader(file);
        BufferedReader reader = new BufferedReader(fileReader);
        StringBuffer buf = new StringBuffer();
        while (reader.ready()) {
            buf.append(reader.readLine() + "\n");
        }
        reader.close();
        return buf.toString();
    }

    private static void parseModuleInfoLine(String line, Module module) {
        if (line.toLowerCase().startsWith("history:")) {
            module.addHistory(line.substring(8));
        } else {
            module.addModuleInfo(line);
        }
    }
}
